2

我正在构建一个简单的 gen_server 模块,它监视多个远程节点的活动

当远程节点注册时,此模块使用 erlang:monitor_node(Node, true) 监视该节点。每个节点仅注册一次(通过日志确认)

在 gen_server 的 handle_info/2 回调中,它捕获 {nodedown, Node} 消息并使用 erlang:monitor_node(Node, false) 监视节点。我希望只收到一次此消息:当远程节点关闭时。

在测试模块时,我发现当一个远程节点宕机时,会向 gen_server 发送数百条 {nodedown, Node} 消息(数量从几百到几千不等)。

为什么monitor_node会发送多条消息?我怎样才能防止这种行为?

编辑:这是(一部分)源代码

register_node(#node_info{node = NodeName} = NodeInfo) ->
    case mnesia:read(node_info, NodeName) of
        [] ->
            monitor_node(NodeName, true),
            error_logger:info_msg("node ~p registered", [NodeName]);
        [_OldInfo] ->
            error_logger:trace_msg("info of node ~p updated", [NodeName])
    end,
    mnesia:write(NodeInfo).

handle_cast({register_node, #node_info{} = NodeStatus}, Timer) ->
    case mnesia:transaction(fun register_node/1, [NodeStatus]) of
        {aborted, Reason} ->
            error_logger:warning_msg("transaction register_node failed: ~p", [Reason]);
        _ ->
        ok
    end,
    {noreply, Timer};
handle_cast({shutdown_node, #node_info{} = NodeStatus}, Timer) ->
    case mnesia:dirty_delete_object(NodeStatus) of
        {aborted, Reason} ->
            error_logger:warning_msg("transaction shutdown_node failed: ~p", [Reason]);
        _ ->
        ok
    end,
    {noreply, Timer};
handle_cast(Message, Timer) ->
    error_logger:warning_msg("~p: received unknown message ~p", [?MODULE, Message]),
    {noreply, Timer}.

handle_info({nodedown, Node}, Timer) ->
    monitor_node(Node, false),
    error_logger:info_msg("~p: node ~p down", [?MODULE, Node]),
    mnesia:transaction(fun mnesia:delete/3, [node_info, Node, write]),
    {noreply, Timer};
handle_info(Message, Timer) ->
    error_logger:warning_msg("~p: received unknown message ~p", [?MODULE, Message]),
    {noreply, Timer}.
4

1 回答 1

5

你已经完成monitor_node(NodeName, true) **INSIDE**了 mnesia 交易。

我认为这是因为 monitor_node 将在内部涉及(I/O 操作)消息通信。这条线不适合放在translation里面。它可能会向'registered'相关节点发送消息。因此,当节点关闭时,'nodedown'已经收到了许多消息。

    If a process has made two calls to monitor_node(Node, true) and Node terminates, 
**two nodedown messages are delivered to the process.** If there is no connection 
to Node, there will be an attempt to create one. If this fails, a nodedown 
message is delivered.

请将该行移出transaction或仅使用"CASE"表达式,然后重试。

register_node(#node_info{node = NodeName} = NodeInfo) ->
    case mnesia:read(node_info, NodeName) of
        [] ->
            monitor_node(NodeName, true),
            error_logger:info_msg("node ~p registered", [NodeName]);
        [_OldInfo] ->
            error_logger:trace_msg("info of node ~p updated", [NodeName])
    end,
    mnesia:write(NodeInfo).
handle_cast({register_node, #node_info{} = NodeStatus}, Timer) ->
    case mnesia:transaction(fun register_node/1, [NodeStatus]) of
        {aborted, Reason} ->
            error_logger:warning_msg("transaction register_node failed: ~p", [Reason]);
        _ ->
        ok
    end,
    {noreply, Timer};

mnesia 交易中的副作用解释

Mnesia 在事务执行时动态设置和释放锁,因此执行带有事务副作用的代码是非常危险的。特别是,事务中的接收语句可能导致事务挂起并且永远不会返回的情况, 这反过来又会导致锁无法释放。这种情况可能会使整个系统陷入停顿,因为在其他进程或其他节点上执行的其他事务被迫等待有缺陷的事务。

于 2012-11-27T09:54:15.430 回答