cri.dev about posts rss

Elixir GenServer name registration

Published on

Here I want to describe what I discovered regarding GenServer and their name registration across the BEAM.

I’ll touch on Process.register/2 and GenServer.start_link/3.

A basic GenServer

Below is a basic GenServer implementation, resembling a key-value store using a GenServer.

(alternatively you could achieve the same with an Agent)

defmodule Store do
  use GenServer

  def start_link(initial_state \\ %{}) do
    GenServer.start_link(__MODULE__, initial_state)
  end

  def set(server, key, value) do
    # ...
    GenServer.cast(server, {:set, key, value})
  end

  def init(state) do
    {:ok, state}
  end

  def handle_cast({:set, key, value}, state) do
    # ...
    {:noreply, state}
  end
end

This Store can now be used like this:

{:ok, store_pid} = Store.start_link()
Store.set(store_pid, :key, 42)

The downside is that to use the Store you need to have a reference to store_pid at hand.

Using Process.register

To give a unique name to a process you can use Process.register/2

Process.register(pid_or_port, name)

Registers the given pid_or_port under the given name.
name must be an atom and can then be used instead of the PID/port identifier when sending messages with Kernel.send/2.

To do so with the Store module, a small change is needed in the init server callback and how you cast a message using the __MODULE__ name:

defmodule Store do
  use GenServer

  ...

  def init(state) do
    Process.register(self, __MODULE__)
    {:ok, state}
  end

  def set(key, value) do
    # ...
    GenServer.cast(__MODULE__, {:set, key, value})
  end

  ...
end

This registers the single process under the given name, namely the atom :store

Now the Store can be used without passing a process reference to the store:

{:ok, _store_pid} = Store.start_link()
Store.set(:key, 42)

In the GenServer.start_link/3 documentation, you can read more about the start_link/3 spec.

The third option is a keyword list, you can use the :name option to specify a name used for register the process.

Check out the docs about “Name registration” too, explaining the different options e.g. registering the process only locally or globally across the whole network nodes.

You can register a GenServer under the module name like so:

defmodule Store do
  use GenServer

  def start_link(initial_state \\ %{}) do
    GenServer.start_link(__MODULE__, initial_state, name: __MODULE__)
  end

  ...
end

Finally, you can use the GenServer without a reference to a process PID:

{:ok, _store_pid} = Store.start_link()
Store.set(:key, 42)

Here, have a slice of pizza 🍕