更改代码后,Elixir的节点间函数调用将失败

时间:2016-07-01 04:32:04

标签: elixir rpc supervisor mnesia

我正在测试节点间函数调用(rpc),如下所示:

defmodule RpcTest do
  def run do 
    Task.Supervisor.async( {DBServer.DistSupervisor, :'dbserver@hostname'},  fn -> "test" end)
    |> Task.await
    |> IO.inspect
  end
end

然后我运行dbserver节点,在Task.Supervisor.start_link(name: DBServer.DistSupervisor)中调用dbserver以接收rcp,并在另一个节点dbclient执行上面的代码

可以正确运行rpc,如下所示。

dbserver

$ iex --sname dbserver --cookie a -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Compiling 1 file (.ex)
Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(dbserver@hostname)1> Task.Supervisor.start_link(name: DBServer.DistSupervisor)
{:ok, #PID<0.101.0>}
iex(dbserver@hostname)2> 

dbclient

$ elixir --sname dbclient --cookie a -S mix run -e RpcTest.run
"test"

但是,在我将代码从"test"更改为"test test后,dbclient节点不起作用。

以下是错误消息

dbserver

iex(dbserver@hostname)2> 
12:54:57.430 [error] Task #PID<0.110.0> started from {:"dbclient@hostname", #PID<13423.52.0>} terminating
** (BadFunctionError) expected a function, got: #Function<0.113878361/0 in RpcTest>
    :erlang.apply/2
    (elixir) lib/task/supervised.ex:94: Task.Supervised.do_apply/2
    (elixir) lib/task/supervised.ex:45: Task.Supervised.reply/5
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Function: #Function<0.113878361/0 in RpcTest>
    Args: []

dbclient

$ elixir --sname dbclient --cookie a -S mix run -e RpcTest.run
Compiling 1 file (.ex)
** (EXIT from #PID<0.52.0>) an exception was raised:
    ** (BadFunctionError) expected a function, got: #Function<0.113878361/0 in RpcTest.run/0>
        :erlang.apply/2
        (elixir) lib/task/supervised.ex:94: Task.Supervised.do_apply/2
        (elixir) lib/task/supervised.ex:45: Task.Supervised.reply/5
        (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

此外,dbserver手动重启后 工作。

有趣的是,无论代码是什么, 都可以使用iex

$ iex --sname dbclient --cookie a -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Compiling 1 file (.ex)
Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(dbclient@hostname)1> Task.Supervisor.async( {DBServer.DistSupervisor, :'dbserver@hostname'},  fn -> "test" end) |>  
...(dbclient@hostname)1> Task.await |>                                      
...(dbclient@hostname)1> IO.inspect                                          
"test"                                       
"test"
iex(dbclient@hostname)2> Task.Supervisor.async( {DBServer.DistSupervisor, :'dbserver@hostname'},  fn -> "test test" end) |> 
...(dbclient@hostname)2> Task.await |>                                      
...(dbclient@hostname)2> IO.inspect  
"test test"                                       
"test test"

我的问题是,

  • 从另一个节点调用该节点中的函数是错误的方法吗?
  • 使用节点间函数调用时,每次更改客户端代码时是否必须重新执行 callee 节点?
  • 为什么iexelixir script之间的行为有所不同?

代码和整个项目都在这里上传:https://github.com/ayamamori/rpc_test/blob/master/lib/rpc_test.ex

提前致谢!

1 个答案:

答案 0 :(得分:1)

IEx中声明的函数与模块中声明的函数之间存在细微差别,即使它们是匿名的。

我们说我们有以下模块:

defmodule FunInfo do
  def info do
    :erlang.fun_info(fn -> "test" end)
  end
end

现在在IEx:

iex(1)> FunInfo.info
[pid: #PID<0.91.0>, module: FunInfo, new_index: 0,
 new_uniq: <<98, 250, 216, 183, 54, 136, 109, 221, 200, 243, 9, 84, 249, 185, 187, 173>>,
 index: 0, uniq: 51893957, name: :"-info/0-fun-0-", arity: 0, env: [], 
 type: :local]

同样的功能,但在IEx中声明:

iex(2)> :erlang.fun_info(fn -> "test" end)
[pid: #PID<0.91.0>, module: :erl_eval, new_index: 20,
 new_uniq: <<103, 57, 49, 11, 11, 201, 159, 65, 226, 96, 121, 97, 18, 82, 151, 208>>,
 index: 20, uniq: 54118792, name: :"-expr/5-fun-3-", arity: 0,
 env: [{[], :none, :none,
   [{:clause, 12, [], [],
     [{:bin, 0,
       [{:bin_element, 0, {:string, 0, 'test'}, :default, :default}]}]}]}],
 type: :local]

我们可以突出一些关于功能信息的内容。对于FunInfo模块中定义的那个,我们有:

  • module: FunInfo
  • :new_uniq::uniq标识符。
  • :env不包含函数的AST。

对于IEx中的定义,我们有:

  • module: :erl_eval
  • :new_uniq::uniq标识符。
  • :env具有匿名函数的AST。

在模块中声明的匿名函数

每次更改匿名函数源代码时,:new_uniq:uniq值都会更改。如果两个节点具有相同模块的不同版本,则此匿名函数将具有不同的:new_uniq:uniq标识符。当您尝试执行匿名函数时,服务器不知道,它会失败。

在IEx

中声明的匿名函数

:new_uniq:uniq值对于您声明的每个函数都不会更改。它们位于已编译的:erl_eval模块中,您不会更改其源代码。此外,IEx中声明的每个函数都在:env中具有AST,因此当您要求远程节点执行该函数时,您也发送函数体。

iex(1)> :erlang.fun_info(fn -> "test" end)
[pid: #PID<0.91.0>, module: :erl_eval, new_index: 20,
 new_uniq: <<103, 57, 49, 11, 11, 201, 159, 65, 226, 96, 121, 97, 18, 82, 151, 208>>,
 index: 20, uniq: 54118792, name: :"-expr/5-fun-3-", arity: 0,
 env: [{[], :none, :none,
       [{:clause, 26, [], [],
         [{:bin, 0,
           [{:bin_element, 0, {:string, 0, 'test'}, :default, :default}]}]}]}],
 type: :local]

如果我创建了另一个匿名函数,则:new_uniq:uniq值不会更改,但:env值会更改为正确的AST。

iex(2)> :erlang.fun_info(fn -> "test test" end)
[pid: #PID<0.91.0>, module: :erl_eval, new_index: 20,
new_uniq: <<103, 57, 49, 11, 11, 201, 159, 65, 226, 96, 121, 97, 18, 82, 151, 208>>,
 index: 20, uniq: 54118792, name: :"-expr/5-fun-3-", arity: 0,
 env: [{[], :none, :none,
       [{:clause, 27, [], [],
         [{:bin, 0,
           [{:bin_element, 0, {:string, 0, 'test test'}, :default, :default}]}]}]}],
 type: :local]

我希望这能回答你的问题。