This is a blog of AmberBit - a Ruby on Rails web development company. Hire us for your project!

Process name registration in Elixir

Elixir, Erlang and processes

In Erlang and Elixir, process is something entirely different than operating system process. In simplest words: it is a hybrid between thread and and object. Ok, maybe that’s not that simple, let’s have a look at example of simple process:

spawn(fn ->
  IO.puts "hello, world!"
  :timer.sleep(10000)
end)

This is basically a function, that sleeps for 10 seconds. It is being started without blocking current process execution, and lives it’s own life. You probably already know that you can send and receive messages from processes. OTP comes with a handy behaviors, such as GenServer that allows us to implement processes that respond to various messages and store state.

We can do all, provided we know the PID of the process. This is the case when we started the process ourselves. But quite often, we want our OTP application to initialize the supervision tree of processes for us, from our application callback module.

lib/project.ex:

defmodule Project do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      supervisor(Project.Endpoint, []),
      supervisor(Project.Repo, []),
      worker(Project.Worker, []),
    ]

    opts = [strategy: :one_for_one, name: Project.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

How do we send messages to our Project.Worker if we are not the ones that start it in the first place? The answer to that problem is name registration.

Naming processes

Elixir and Erlang allow us to name processes. By giving process name, we associate PID with certain atom.

Let’s have a look at our worker:

lib/worker.ex:

defmodule Project.Worker do
  use GenServer

  def start_link(_ignore \\ nil) do
    GenServer.start_link(__MODULE__, nil, [])
  end

  def handle_call("Hi!", _from, state) do
    {:reply, "Hola!", state}
  end
end

We can spawn such worker and communicate with it using it’s PID:

{:ok, pid} = Project.Worker.start_link
{:ok, #PID<0.97.0>}
iex(4)> GenServer.call(pid, "Hi!")
"Hola!"

But if if this worker is started by our application’s callback module, inside a supervision tree, how do we find it?

Elixir allows us to do simple name registration for our processes. We can do it manually:

iex(1)> {:ok, pid} = Project.Worker.start_link
{:ok, #PID<0.93.0>}
iex(2)> Process.register pid, Project.Worker
true
iex(3)> pid = Process.whereis(Project.Worker)
#PID<0.93.0>
iex(4)> GenServer.call(pid, "Hi!")
"Hola!"

And that works great. But OTP behaviors allow us to do it even simpler, by passing name in place of PID in various places:

iex(5)> GenServer.call(Project.Worker, "Hi!")
"Hola!"

Sweet.

Note: Project.Worker is an atom. In Elixir module names are atoms. It is a common pattern to name processes that have only instance of given module by it’s module name, but we do not have to. We can register our processes under any atom, but a process can have only one name:

iex(1)> {:ok, pid} = Project.Worker.start_link
{:ok, #PID<0.93.0>}
iex(2)> Process.register pid, :some_atom      
true
iex(3)> Process.register pid, :some_other_atom
** (ArgumentError) argument error
             :erlang.register(:some_other_atom, #PID<0.93.0>)
    (elixir) lib/process.ex:338: Process.register/2

You can’t register two processes under the same name either:

iex(1)> {:ok, pid1} = Project.Worker.start_link
{:ok, #PID<0.93.0>}
iex(2)> {:ok, pid2} = Project.Worker.start_link
{:ok, #PID<0.95.0>}
iex(3)> Process.register pid1, Project.Worker
true
iex(4)> Process.register pid2, Project.Worker
** (ArgumentError) argument error
             :erlang.register(Project.Worker, #PID<0.95.0>)
    (elixir) lib/process.ex:338: Process.register/2

OTP makes it easier

When we let our supervisors manage our processes, we do not need to manually Process.register each of the processes. The functionality is built into OTP. We need to allow our worker to register it’s name on start up:

defmodule Project.Worker do
  use GenServer

  def start_link(name \\ nil) do
    GenServer.start_link(__MODULE__, nil, [name: name])
  end

  def handle_call("Hi!", _from, state) do
    {:reply, "Hola!", state}
  end
end

And then spawn a named worker from our application callback module:

defmodule Project do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      worker(Project.Worker, [Project.Worker]),
    ]

    opts = [strategy: :one_for_one, name: Project.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

This is enough to find our process by it’s name and call it:

iex(1)> GenServer.call Project.Worker, "Hi!"
"Hola!"

Of course this way you can start multiple workers, such as:

defmodule Project do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      worker(Project.Worker, [Project.Worker1]),
      worker(Project.Worker, [Project.Worker2]),
      worker(Project.Worker, [Project.Worker3]),
    ]

    opts = [strategy: :one_for_one, name: Project.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

…but this becomes inconvenient as soon as you want to dynamically spawn and shut down your workers. For such complex behaviors, you should either use built in :pg2 process registry, :glob global process registry or very powerful and flexible :grpoc.

This is it for now, more about alternative ways to register processes soon! Stay tuned for next blog posts on the subject!

by Hubert Łępicki, twitter: @hubertlepicki

Do you need skilled professionals to help you build Rails applications? Hire us for your project!
comments powered by Disqus

Want to get in touch? Drop us a line!