监督树中的名称解析

Name Resolution in Supervision Tree

我正在尝试通过 elixir 1.9 中的注册名称解析由应用程序管理的 GenServer。下面是一些示例代码:

defmodule Experiment do
  use Application

  def start(_type, _args) do
    child_specs = [
      config_spec()
    ]

    Supervisor.start_link(child_specs, name: Experiment, strategy: :one_for_one)
  end

  defp config_spec() do
    %{
      id: Experiment.Data,
      start: {
        Experiment.Data,
        :start_link,
        [
          "some data",
           # set name of child process here
          [:name, Experiment.Data]
        ]
      }
    }
  end
end

defmodule Experiment.Data do
  use GenServer
  require Logger

  def start_link(data, options \ []) do
    Logger.info("starting with data '#{data}' and options [#{Enum.join(options, ", ")}]")
    GenServer.start_link(__MODULE__, data, options)
  end

  @impl true
  def init(data) do
    Logger.info("starting")
    {:ok, data}
  end

  @impl true
  def handle_call(:data, _from, state) do
    {:reply, state, state}
  end

  @impl true
  def terminate(reason, _state) do
    Logger.info("terminated: #{reason}")
  end

  def get_data(server) do
    GenServer.call(server, :data)
  end
end

我已经在 iex 中测试过了 (iex -S mix):

iex(1)> {:ok, sup_pid} = Experiment.start(nil, nil)     

10:45:36.914 [info]  starting with data 'some data' and options [name, Elixir.Experiment.Data]

10:45:36.916 [info]  starting
{:ok, #PID<0.189.0>}
iex(2)> Experiment.Data.get_data(Experiment.Data)       
** (exit) exited in: GenServer.call(Experiment.Data, :data, 5000)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (elixir) lib/gen_server.ex:979: GenServer.call/3
iex(2)> Experiment.Data.get_data(Elixir.Experiment.Data)
** (exit) exited in: GenServer.call(Experiment.Data, :data, 5000)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (elixir) lib/gen_server.ex:979: GenServer.call/3
iex(2)> send(Experiment.Data, {self(), "hi"})           
** (ArgumentError) argument error
    :erlang.send(Experiment.Data, {#PID<0.187.0>, "hi"})
iex(2)> send(Experiment, {self(), "hi"})     
{#PID<0.187.0>, "hi"}
iex(3)> 
10:46:05.410 [error] Supervisor received unexpected message: {#PID<0.187.0>, "hi"}

这里让我感到困惑的是我试图用名字 Experiment.Data 注册 Experiment.Data。但是当我尝试向名为 Experiment.Data 的进程发送消息时,找不到它。我添加了 terminate 钩子来检查它是否早死,但这似乎没有被调用。我添加了日志记录行,显示进程正在启动。我也试过通过注册名称调用应用程序,这很有效。然而,通过名称调用它的 child (Experiment.Data) 不会。我了解 child 进程的启动方式 seems to have changed recently,这似乎引起了一些混乱。我做错了什么?

编辑

将 config_spec 函数更改为此似乎已允许它工作:

  defp config_spec() do
    %{
      id: Experiment.Data,
      start: {
        Experiment.Data,
        :start_link,
        [
          "some data",
          [
            {:name, Experiment.Data}
          ]
        ]
      }
    }
  end

GenServer.start_link 想要选项的关键字列表,或者换句话说,一个二元组列表。 [:name, Experiment.Data]是两个原子的列表,所以不生效。您可以将其写为 [{:name, Experiment.Data}] 或使用关键字列表的特殊语法 [name: Experiment.Data].