Search code examples
elixirgen-server

Name Resolution in Supervision Tree


I'm trying to resolve a GenServer managed by an Application by a registered name in elixir 1.9. Here's some example code:

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

I've tested this in 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"}

What's confusing me here is that I've tried to register Experiment.Data with the name Experiment.Data. But when I try to send a message to the process named Experiment.Data it can't be found. I've added the terminate hook to check it's not dying early, but this doesn't seem to be called. I've added logging lines which show me that the process is being started. I've also tried calling the application by registered name, which works. However calling its child (Experiment.Data) by name does not. I understand the way child processes are started seems to have changed recently which seems to have caused some confusion. What am I doing wrong?

EDIT

Changing the config_spec function to this seems to have allowed it to work:

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

Solution

  • GenServer.start_link wants a keyword list for options, or in other words a list of 2-tuples. [:name, Experiment.Data] is a list of two atoms, so it doesn't take effect. You can write it either as [{:name, Experiment.Data}] or using special syntax for keyword lists [name: Experiment.Data].