2 个节点之间的 BEAM 行为 RPC 调用
BEAM behaviour RPC call between 2 nodes
我有2个节点,
暂且称他们为A和B吧。
B 有一个 GenServer 模块,启动时监控 A。(此模块仅存在于 B 上)
当A连接到B时,这个GenServer由A启动。
如果 A 在连接到 B 时死亡,B 应该使用 :init.stop()
自杀
这是 GenServer 的代码:
defmodule Monitor do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, [], [])
end
def init([]) do
{:ok, %{}, 0}
end
def handle_info(:timeout, s) do
start(:"A@127.0.0.1")
{:noreply, s}
end
def handle_info({:nodedown, node}, state) do
s_node = node |> to_string
case s_node do
"A" <> _ ->
IO.puts "A is down, killing myself !"
:init.stop()
_ ->
:ko
end
{:noreply, state}
end
def handle_info(_, s) do
{:noreply, s}
end
def start(node) do
res = Node.monitor(node, true)
IO.puts "Starting to monitor: #{inspect node}"
end
end
我同时启动节点 A 和 B。我将 A 连接到 B。我在 A 中使用此命令启动监视器:
> :rpc.call(:"B@127.0.0.1", Monitor, :start_link, [])
{:ok, #PID<8440.594.0>}
如果我使用 Node.disconnect
从 B 正常断开 A
但是,如果我用 Ctrl-C Ctrl-C
甚至 Ctrl-g / q
杀死 A 的控制台
B 上 pid <0.594.0>
的 GenServer 不再存在,因此无法检测到 A 已关闭。
由于某种原因,Pid "linked" 是 A 吗?
PS
我尝试使用 Node.spawn
并在 :rpc.call 中调用 spawn,我得到相同的结果
PS 2
如果我从 B 控制台启动 GenServer,当使用 Node.disconnect
或 Ctrl-C Ctrl-C
杀死 A 时,它会按预期工作...
PS 3
我认为这可能来自我调用 :start_link 的事实,但我与 :start 有相同的行为(没有 link)
补充问题
Hynek -Pichi- Vychodil
在评论中回答...
为什么所有 IO.puts 都打印在 A 上,而 GenServer 在技术上 运行 打印在 B 上?
当您调用 :rpc.call(:"B@127.0.0.1", Monitor, :start_link, [])
时,您 link 您的 Monitor
进程到当前 shell(并将 group_leader
设置为 :user
在 A
处处理,这基本上会阻止你在 A 关闭后看到输出 IO.puts "A is down, killing myself !"
。)所以这意味着当 A 死亡时,A 处的 shell 运行 也会死亡,你的 Monitor
也是因为 linked。
所以首先,你应该使用 GenServer.start()
作为开始 :rpc.call()
或将进程标志 :trap_exit
设置为 true 或将其放在节点 B 的应用程序 运行 的主管树中. 第二种,在节点B处设置group_leader
到user
进程
P.S.: 我不熟悉 Elixir 语法所以我不提供代码。
我有2个节点,
暂且称他们为A和B吧。
B 有一个 GenServer 模块,启动时监控 A。(此模块仅存在于 B 上) 当A连接到B时,这个GenServer由A启动。
如果 A 在连接到 B 时死亡,B 应该使用 :init.stop()
这是 GenServer 的代码:
defmodule Monitor do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, [], [])
end
def init([]) do
{:ok, %{}, 0}
end
def handle_info(:timeout, s) do
start(:"A@127.0.0.1")
{:noreply, s}
end
def handle_info({:nodedown, node}, state) do
s_node = node |> to_string
case s_node do
"A" <> _ ->
IO.puts "A is down, killing myself !"
:init.stop()
_ ->
:ko
end
{:noreply, state}
end
def handle_info(_, s) do
{:noreply, s}
end
def start(node) do
res = Node.monitor(node, true)
IO.puts "Starting to monitor: #{inspect node}"
end
end
我同时启动节点 A 和 B。我将 A 连接到 B。我在 A 中使用此命令启动监视器:
> :rpc.call(:"B@127.0.0.1", Monitor, :start_link, [])
{:ok, #PID<8440.594.0>}
如果我使用 Node.disconnect
从 B 正常断开 A
但是,如果我用 Ctrl-C Ctrl-C
甚至 Ctrl-g / q
杀死 A 的控制台
B 上 pid <0.594.0>
的 GenServer 不再存在,因此无法检测到 A 已关闭。
由于某种原因,Pid "linked" 是 A 吗?
PS
我尝试使用 Node.spawn
并在 :rpc.call 中调用 spawn,我得到相同的结果
PS 2
如果我从 B 控制台启动 GenServer,当使用 Node.disconnect
或 Ctrl-C Ctrl-C
杀死 A 时,它会按预期工作...
PS 3 我认为这可能来自我调用 :start_link 的事实,但我与 :start 有相同的行为(没有 link)
补充问题
Hynek -Pichi- Vychodil
在评论中回答...
为什么所有 IO.puts 都打印在 A 上,而 GenServer 在技术上 运行 打印在 B 上?
当您调用 :rpc.call(:"B@127.0.0.1", Monitor, :start_link, [])
时,您 link 您的 Monitor
进程到当前 shell(并将 group_leader
设置为 :user
在 A
处处理,这基本上会阻止你在 A 关闭后看到输出 IO.puts "A is down, killing myself !"
。)所以这意味着当 A 死亡时,A 处的 shell 运行 也会死亡,你的 Monitor
也是因为 linked。
所以首先,你应该使用 GenServer.start()
作为开始 :rpc.call()
或将进程标志 :trap_exit
设置为 true 或将其放在节点 B 的应用程序 运行 的主管树中. 第二种,在节点B处设置group_leader
到user
进程
P.S.: 我不熟悉 Elixir 语法所以我不提供代码。