来自 rpc 调用到其他节点的错误?

a bug from rpc call to other node?

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

-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) 的进程,这是真的吗?如果是的话,为什么我在客户端节点上有这个错误?

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

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

inverse 函数向执行指令 1/a 并崩溃的 serveur: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)

的远程过程调用中使用超时是个好主意

在之前的 post 中,您试图使用 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> 

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

解决了这个问题
process_flag(trap_exit, true),
link(whereis(ownServer)),

inverse 函数的头部,我添加

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

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