【问题标题】:a bug from rpc call to other node? [duplicate]从 rpc 调用到其他节点的错误? [复制]
【发布时间】:2020-06-13 17:56:26
【问题描述】:

我在同一台 Windows 机器上创建了 2 个 erlang 节点,带有两个 cmd 窗口:'unclient@MYPC' 和 'unserver@MYPC' ,服务器代码非常简单:

-module(serveur).
-export([start/0,recever/0,inverse/1]). 
%%%%
start() ->
Pid=spawn(serveur,recever,[]), 
register(ownServer, Pid). 
%%%%
recever() -> 
receive 
{From, X} ->From ! {ownServer,1/X} end. 
%%%%
inverse(X) -> 
ownServer!{self(),1/X}
 receive
{ownServer, Reply} ->Reply end. 

所以在服务器节点 cmd 我启动这个模块

c(serveur). 
serveur:start() 

在客户端节点我使用 rpc 调用函数尝试连接,一切都很好,例如我尝试:

rpc:call(unserveur@MYPC,serveur,inverse,[2]).

我收到了 0.5 现在我使用原子将其发送到服务器以导致错误

rpc:call(unserveur@MYPC,serveur,inverse,[a]).

在客户端 cmd 节点: 我等待来自服务器的响应,但我没有收到任何东西,也没有更多的客户端提示:

unclient@MYPC 1>

我可以写,但是 shell 不再执行我的指令,也没有任何提示。

我搜索了一下,发现rpc:call 触发目标节点的 rex 服务器生成并监视执行 (M,F,A) 的进程,这是真的吗?如果是,为什么我在客户端节点上有这个错误?

【问题讨论】:

    标签: scala go erlang elixir


    【解决方案1】:

    在非客户端,rpc:call(Node,serveur,inverse,[a]) 为 Node rpc 服务器构建一条消息并等待响应。

    在unserver端,RPC服务器接收到消息并启动一个进程来调用函数serveur:inverse(a)

    inverse 函数向执行指令1/aserveur:recever() 发送消息并崩溃。

    因此,无法将回复消息发送回 inverse。反函数将永远等待答案,以及非客户端节点上的 rpc:call,因为您没有定义任何超时。

    您可以在反函数中定义超时:

    inverse(X) -> 
        ownServer!{self(),1/X}
        receive
            {ownServer, Reply} -> {ok,Reply} 
        after 100 -> % define a timout of 100 ms
            {error,timeout}
        end. 
    

    另外,使用rpc:call(Node, Module, Function, Args, Timeout)在远程过程调用中使用超时是个好主意

    在上一篇文章中,您尝试使用 trap_exit 标志获得响应。那里有几个错误。首先正如@legoscia 所解释的,如果出现错误,退出消息将发送到任何链接的进程。第二个是您期望您的进程将继续执行其代码。发生错误时,进程立即停止,系统发出退出消息,该消息将终止或将被所有链接的进程接收,具体取决于标志 trap_exit 的值。

    我写了一个符合你预期的版本:

    -module(serveur).
    -export([start/0,recever/0,inverse/1]). 
    -export([do_op/3]).
    
    %%%%
    start() -> 
        Pid=spawn(serveur,recever,[]), 
        register(ownServer, Pid). 
    
    %%%%
    recever() -> 
        process_flag(trap_exit,true), 
        receive
            stop -> stopped;
            {'EXIT',_,_} -> recever(); % necessary to throw the {EXIT,_,normal} messages
            {From, Op, X} -> 
                spawn_link(serveur, do_op, [self(),Op,X]),
                receive 
                    Reply -> From ! {ownServer, Reply}  
                end,
                recever()
        end. 
    
    do_op(From, inverse, X) ->
        From ! {result,1/X}.
    
    %%%%
    inverse(X) -> 
        ownServer!{self(), inverse, X}, 
        receive 
            {ownServer, Reply} ->Reply 
        end. 
    

    事实上,这段代码的工作方式或多或少类似于catch 语句,这正是您想要做的,也是您应该在那里使用的。在erlang中,当意外发生时让进程崩溃是一个非常好的主意,特别是使用Erlang OTP机制,但是当错误很可能时(例如用户界面)我认为它更适合使用catch或try/catch在正确的级别。

    [编辑]

    您想完全了解系统的行为,这很酷。要回答你的问题,很抱歉,我从不使用 rpc,也不知道它适合哪些情况。

    对于这种情况,我使用允许集群节点之间通信的全局库(请参阅erlang distribution from learnyousomeerlang a very good site to learn and understand erlang)。

    正如您所说,您解决问题的方式使用了大量代码(我不确定它现在是否可以在本地运行)。在我看来,这是因为标志 trap_exit 不是用于此用途,而是用于 OTP 主管树和所有 otp 行为(请参阅What is OTP from the same site)。在您的情况下,您应该使用 catch 语句并添加超时来处理可能的错误。这是处理错误参数和重载服务器的代码。我添加了一些接口来模拟不同的用例。

    -module(serveur).
    
    -export([start/0,recever/0,inverse/1,lock/0,unlock/0,stop/0,wait10s/0]). 
    
    %%%%
    start() ->
        Pid=spawn(serveur,recever,[]), 
        register(ownServer, Pid). 
    
    %%%%
    recever() -> 
        receive 
            {From, X} ->
                From ! {ownServer,(catch 1/X)},
                recever();
            waitForUnlock ->
                ok = wait_for_unlock(),
                recever();
            stop -> server_stopped
        end.
    
    %%%%
    inverse(X) -> 
        ownServer ! {self(),X},
        receive
            {ownServer, {'EXIT',{Reply,_}}} ->
                {error,Reply};      
            {ownServer, Reply} ->
                {ok,Reply}
        after 100 ->
            {error,timeout}
        end.
    
    %%%% use this interface to simulate an overloaded server 
    lock() ->
        ownServer ! waitForUnlock.
    
    %%%% use this interface to unlock the server
    unlock() ->
        ownServer ! unlock.
    
    %%%% use this interface to simulate a very long answer from server
    wait10s() ->
        timer:sleep(10000),
        iAmAwake.
    
    %%%% use this interface to stop the server
    stop() ->
        ownServer ! stop.
    
    %%%% private function used to hang the server
    wait_for_unlock() ->
        receive
            unlock -> ok
        end.
    

    本地节点测试

    (unserveur@MyPc)1> c(serveur).
    {ok,serveur}
    (unserveur@MyPc)2> serveur:start().
    true
    (unserveur@MyPc)3> serveur:inverse(2).
    {ok,0.5}
    (unserveur@MyPc)4> serveur:inverse(a).
    {error,badarith}
    (unserveur@MyPc)5> serveur:lock().   
    waitForUnlock
    (unserveur@MyPc)6> serveur:inverse(2).
    {error,timeout}
    (unserveur@MyPc)7> serveur:inverse(a).
    {error,timeout}
    (unserveur@MyPc)8> serveur:unlock().  
    unlock
    (unserveur@MyPc)9> serveur:inverse(2).
    {ok,0.5}
    (unserveur@MyPc)10> serveur:wait10s(). 
    iAmAwake
    (unserveur@MyPc)11> serveur:stop().   
    stop
    (unserveur@MyPc)12> serveur:inverse(2).
    ** exception error: bad argument
         in function  serveur:inverse/1 (serveur.erl, line 60)
    (unserveur@MyPc)13>
    

    和(几乎)来自客户端节点的相同测试

    (unclient@MyPc)1> net_adm:ping(unserveur@MyPc).
    pong
    (unclient@MyPc)2> rpc:call(unserveur@MyPc,serveur,start,[]).
    true
    (unclient@MyPc)3> rpc:call(unserveur@MyPc,serveur,inverse,[2]).
    {ok,0.5}
    (unclient@MyPc)4> rpc:call(unserveur@MyPc,serveur,inverse,[a]).
    {error,badarith}
    (unclient@MyPc)5> rpc:call(unserveur@MyPc,serveur,lock,[]).    
    waitForUnlock
    (unclient@MyPc)6> rpc:call(unserveur@MyPc,serveur,inverse,[2]).
    {error,timeout}
    (unclient@MyPc)7> rpc:call(unserveur@MyPc,serveur,inverse,[a]).
    {error,timeout}
    (unclient@MyPc)8> rpc:call(unserveur@MyPc,serveur,unlock,[]).  
    unlock
    (unclient@MyPc)9> rpc:call(unserveur@MyPc,serveur,inverse,[2]).
    {ok,0.5}
    (unclient@MyPc)10> rpc:call(unserveur@MyPc,serveur,wait10s,[]). 
    iAmAwake
    (unclient@MyPc)11> rpc:call(unserveur@MyPc,serveur,wait10s,[],1000).
    {badrpc,timeout}
    (unclient@MyPc)12> rpc:call(unserveur@MyPc,serveur,stop,[]).        
    stop
    (unclient@MyPc)13> rpc:call(unserveur@MyPc,serveur,inverse,[2]).    
    {badrpc,{'EXIT',{badarg,[{serveur,inverse,1,
                                      [{file,"serveur.erl"},{line,60}]},
                             {rpc,'-handle_call_call/6-fun-0-',5,
                                  [{file,"rpc.erl"},{line,197}]}]}}}
    (unclient@MyPc)14> 
    

    【讨论】:

    • 谢谢你帕斯卡,你说的都是真的,所以我用一些东西代替了 timoeout 我在反函数的头部添加了process_flag(trap_exit, true), link(ownServer),我在接收部分添加了{'EXIT',_, _} ->apply(serveur, start, []).这使得运行反向捕获 serverur:recever 进程的退出的 rex 进程,他可以重新启动它,但这不起作用,我有同样的错误,你怎么解释?谢谢你,先生。
    【解决方案2】:

    是的,最后我解决了这个错误,最重要的是我明白会发生什么: 当我调用rpc:call('unserveur@MYPC',serveur,inverse,[a])时,客户端节点进程(主shell进程)将此消息发送到服务器节点进程(主shell进程),服务器节点进程将此消息发送到服务器节点的rex服务器,rex服务器产生并监控一个将运行apply(serveur, inverse, [a])的新进程,这个新进程运行该函数,运行recever()的serverur进程将崩溃并且没有回复将永远等待的新进程,他后面的所有进程将永远等待包括主shell客户端节点进程,这解释了提示符的消失和正常写入。这正是帕斯卡所说的,所以你已经回答了我的问题。 我通过添加解决了这个问题

    process_flag(trap_exit, true),
    link(whereis(ownServer)),
    

    inverse函数的头部,我添加

    {'EXIT', _, _} -> start(),
                             sorry;
    

    inverse 函数的receive 会话的开头,当我用原子调用rpc 调用时,我可以在客户端节点shell 看到抱歉,服务器自动返回再次工作,所以当我要求第二次 rpc 调用 inverse 与整数我有正确的答案。 我看到我为这个调用编写了很多代码,所以可能是 rpc 调用不是一个好的选择,用手动生成进程替换它会更好,你怎么看?

    【讨论】:

    • 很好,我编辑了我的帖子来回答(或多或少)你的最后一个问题。
    • 谢谢帕斯卡,我看到你用 catch 语句解决了这个错误,这很好,但为什么乔建议 erlang 程序员不要使用防御性编程,而是用“让它崩溃”来代替它?如果你能给我一个主管和工人树的小例子,因为在书中我只看到了没有明确例子的概念
    • 在我的理解中,我们可以将错误分为两类:一类是可能的,通常是由于人为交互,另一类是由于故障或错误而出乎意料。对于第一类,我认为防御性编程是可以的,因为当您编写程序时,您知道人工输入是什么,并且如果您发回适当的消息,用户将能够更正输入。在您的情况下,检查的最佳位置是在逆函数中,这样您就不会干扰服务器(例如inverse(X) when is_number(X), not(X == 0) -> ...
    • 对于第二种错误,你不知道在哪里测试什么。此外,问题将需要更正(错误)、重新启动(瞬态故障,如不良 DDR 访问、内存损坏)或新部署(硬件故障、电源故障)。对于这类错误,让崩溃并使用 OTP 提供的重启策略会更有效。而不是到处检查,不知道该怎么做。最后一点,也可以像您一样对第一种错误使用 let it crash 策略,但它的可读性可能较差。
    • 谢谢帕斯卡,我想我明白这一点。但就在我之前的例子中:rex 服务器产生了一个新的进程工作者并对其进行监控,这是否是主管和工作者树的概念?如果不能,你能给我举个例子吗?
    猜你喜欢
    • 2019-08-27
    • 1970-01-01
    • 2015-01-07
    • 1970-01-01
    • 2022-07-07
    • 2021-10-03
    • 2020-11-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多