gen_server:call 未注册的全局名称

gen_server:call on a global name that is not registered

我有一个 gen_server 进程注册了这样一个全局名称:

global:register_name(<<"CLIENT_", NAME/binary>>, self()),

另一个进程正在尝试使用 gen_server:call 向此进程发送消息,如下所示:

 gen_server:call({global, <<"CLIENT_", NAME/binary>>}, {msg, DATA}),

如果第二次调用发生在第一个进程注册全局名称之前,它会死于:

exit with reason {noproc,{gen_server,call,[{global,<<"CLIENT_122">>},{msg, <<"TEST">>}]}}

只有注册了全局名才调用,没有注册则做其他事情的正确方法是什么?

三件事:

  1. 如何守卫这个电话(机制)。
  2. 为什么您通常 不希望 保护调用(稳健的架构)。
  3. 您将接口放置到此函数的位置(代码结构)。

力学

您可以在调用之前检查名称是否已在全局注册表中注册:

-spec send_message(Name, Message) -> Result
    when Name    :: term(),
         Message :: term(),
         Result  :: {ok, term()}
                  | {error, no_proc}.

send_message(Name, Message) ->
    case global:whereis_name(Name) of
        undefined ->
            {error, no_proc};
        PID ->
            Value = gen_server:call(PID, Message),
            {ok, Value}
    end.

因为检查 global:whereis_name/1 的 return 值和通过 gen_server:call/2,3 实际调用之间会有几纳秒,所以你 仍然 不知道您是否真的只是将调用发送到死进程,但至少您将它发送到不会立即使程序崩溃的 PID。

另一种方法是使用 try ... catch 构造,但这是一个很难养成的习惯。

稳健的架构

上面的所有内容,请记住,但如果此名称未注册,您应该希望崩溃。 你的注册进程应该是活的,那你为什么这么偏执?!?如果事情不好,你知道他们在一种灾难性的方式,让与那次崩溃有关的一切立即燃烧。 不要尝试在未知状态下自行恢复,这就是主管的作用。让您的系统在已知状态下重新启动再试一次。如果这是用户主导的操作(系统的某些用户,或网页请求或其他)他们会再试一次,因为他们是猴子,会多次尝试。如果它是自动请求(例如,用户是计算机或机器人),它可以重试或不重试,但在常见情况下由它自己决定——但给它一些失败指示(错误消息,一个封闭的套接字等)。

只要您调用的进程在其 init/1 调用期间注册了它的名称(在它 return 将自己的 PID 发送给它的主管之前),并且这种情况总是发生 在调用进程处于活动状态或知道要调用的进程之前 那么你应该不会有任何问题。如果它由于某种原因崩溃了,那么你的程序就会有更多的基本问题,捕捉调用者的崩溃对你没有帮助。这是鲁棒性工程中的一个基本思想。

构造您的系统,以便保证被调用者在调用发生之前还活着并已注册,如果它已经死亡,您应该希望 调用者也死亡。 (是的,我在打死马,但这很重要。)

代码结构

大多数时候你不希望有一个定义进程的模块,假设 foo.erl 定义了一个我们将命名为 {global, "foo"} 的进程,有一个裸调用 gen_server:call/2,3gen_server:cast/2 用于另一个模块中定义的单独进程(假设 bar.erl 定义了我们将命名为 {global, "bar"} 的进程)。我们想要的是 bar.erl 有一个它导出的接口函数,并且这个函数是 gen_server:call/2 但发生的地方。

这样,任何适用于此调用的特殊工作(任何其他调用模块也可能需要)都存在于一个位置,并且您可以将进程的接口命名为 "bar"除了传递给它的消息之外还有一些意义。

例如,如果 bar.erl 定义的进程是一个连接计数器(也许我们正在编写一个游戏服务器并且我们正在计算连接数)我们可能会让 bar.erl 负责维护柜台。因此,只要有新用户连接,进程就会向 bar 发送 cast(异步消息)。与其让每个可能需要执行此操作的不同进程定义一些复杂的名称检查,然后再发送裸消息,不如考虑从 bar.erl 导出一个函数,该函数隐藏该混乱并命名为有意义的东西,例如 bar:notify_connect().在你的其他代码中调用它更容易理解,你可以选择你应该如何处理这种 "what if bar doesn't exist?" 情况,就在那里,在一个地方。

关于这一点,您可能想看看基本的 Erlang "service manager -> worker" pattern。在许多情况下,并不是绝对需要命名进程。