来自 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 调用可能不是一个好的选择,用手动生成进程替换它会更好,你怎么看?
我在同一台 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 调用可能不是一个好的选择,用手动生成进程替换它会更好,你怎么看?